
#include <cassert>
#include <cstdlib>
#include <math.h>

#include <Windows.h>

#include <assimp\Importer.hpp>
#include <assimp\scene.h>
#include <assimp\postprocess.h>

#include "Core\Camera.h"
#include "Core\Engine.h"
#include "Core\Globals.h"
#include "Core\Input.h"
#include "Core\Renderer.h"
#include "Core\RenderTarget.h"
#include "Scene\Material.h"
#include "Scene\Scene.h"
#include "Shaders\DeferredlightingShader.h"
#include "Shaders\HDRShader.h"
#include "Shaders\MPMLShader.h"
#include "Shaders\RT2ScreenShader.h"
#include "Shaders\SPMLShader.h"

#pragma comment(lib, "Winmm.lib")


Engine* Engine::s_engine_instance = NULL;


UINT Engine::s_loop_delay = 30;

 
Engine& Engine::GetInstance()
{
	// Lazy initialization.
	if(!s_engine_instance)
	{
		s_engine_instance = new Engine;
		assert(s_engine_instance);
	}

	return *s_engine_instance;
}


BOOL Engine::IsValid() const
{
	return m_initialized;
}


BOOL Engine::Initialize()
{
	BOOL result = FALSE;

	// Initialize windows.
	InitializeWindows();

	// Initialize input.
	m_input = new Input;
	assert(m_input);

	result = m_input->Initialize(m_app_instance, m_app_window);
	assert(result);

	// Initialize renderer.
	m_renderer = new Renderer;
	assert(m_renderer);

	result = m_renderer->Initialize(m_app_window, m_window_size, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED);
	assert(result);

	// Initialize extras.
	InitializeExtras();

	// Initialize shaders.
	InitializeShaders();

	// Initialialize scene.
	InitializeScene();

	// Set initialization flag.
	m_initialized = TRUE;

	return true;
}


VOID Engine::Terminate()
{
	// Terminate scene.
	TerminateScene();

	// Terminate shaders.
	TerminateShaders();

	// Terminate extras.
	TerminateExtras();

	// Terminate renderer.
	if(m_renderer)
	{
		m_renderer->Terminate();

		delete m_renderer;
		m_renderer = NULL;
	}

	// Terminate input.
	if(m_input)
	{
		delete m_input;
		m_input = NULL;
	}

	// Terminate windows.
	TerminateWindows();

	// Set initialization flag.
	m_initialized = FALSE;
}


UINT Engine::GetUpdateDelay()
{
	return s_loop_delay;
}


CONST Camera& Engine::camera() CONST
{
	return *m_camera;
}

Size Engine::GetSize() CONST
{
	RECT metrics;
	GetWindowRect(m_app_window, &metrics);

	CONST INT width = (metrics.right - metrics.left);
	CONST INT height = static_cast< INT >(metrics.bottom - metrics.top);
	
	return Size(width, height);
}


VOID Engine::Run()
{
	// Initialize message structure.
	MSG message;
	ZeroMemory(&message, sizeof(MSG));

	// Message loop.
	BOOL done = FALSE;
	UINT timestamp = timeGetTime();

	while(!done)
	{
		BOOL result = FALSE;

		// Fixed update rate, variable fps.
		UINT dt = timeGetTime() - timestamp;
		
		if(dt >= s_loop_delay)
		{
			timestamp = timeGetTime();

			while(dt >= s_loop_delay)
			{
				dt -= s_loop_delay;

				result = Update();
				assert(result);
			}
		}
		
		m_fps = timeGetTime();
		result = Render();
		assert(result);
		m_fps = timeGetTime() - m_fps;
		m_fps = 1000 / (m_fps + 0.0001f);
	
		// Handle windows messages.
		if(PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&message);
			DispatchMessage(&message);
		}

		if(message.message == WM_QUIT)
		{
			done = TRUE;
		}
	}
}


Engine::Engine()
	: m_app_name(NULL),
      m_window_size(800, 600),
	  m_fps(0),
	  m_renderer(NULL),
	  m_input(NULL),
	  m_render_type(SPML),
	  m_hdr(FALSE),
	  m_hdr_target(NULL),
	  m_rt(COMPOSITION),
	  m_spml_shader(NULL),
	  m_mpml_shader(NULL),
	  m_gbuffer_shader(NULL),
	  m_deflight_shader(NULL),
	  m_rt2screen_shader(NULL),
	  m_hdr_shader(NULL),
	  m_camera(NULL),
	  m_scene(NULL),
	  m_initialized(FALSE)
{
	for(UINT8 i = 0; i < GBUFFER_RT_COUNT; ++i)
		m_gbuffer[i] = NULL;
}


Engine::~Engine()
{
	if(m_initialized)
		Terminate();
}


BOOL Engine::InitializeWindows()
{
	// Get application's instance.
	m_app_instance = GetModuleHandle(NULL);

	// Set application's name.
	m_app_name = TEXT("Deferred Rendering Demo");

	// Setup the window class.
	WNDCLASSEX window_class;

	window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	window_class.lpfnWndProc = WndProc;
	window_class.cbClsExtra = 0;
	window_class.cbWndExtra = 0;
	window_class.hInstance = m_app_instance;
	window_class.hIcon = LoadIcon(NULL, IDI_WINLOGO);
	window_class.hIconSm = window_class.hIcon;
	window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
	window_class.hbrBackground = static_cast< HBRUSH >(GetStockObject(BLACK_BRUSH));
	window_class.lpszMenuName = NULL;
	window_class.lpszClassName = m_app_name;
	window_class.cbSize = sizeof(WNDCLASSEX);

	// Register the window class.
	RegisterClassEx(&window_class);

	// Create window and get handle.
	D3DXVECTOR2 window_position(static_cast< FLOAT >(GetSystemMetrics(SM_CXSCREEN) - m_window_size.GetWidth()) / 2,
							    static_cast< FLOAT >(GetSystemMetrics(SM_CYSCREEN) - m_window_size.GetHeight()) / 2);

	m_app_window = CreateWindowEx(WS_EX_APPWINDOW, m_app_name, m_app_name, 
								  WS_CAPTION | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP | WS_SYSMENU | WS_MAXIMIZEBOX,
								  static_cast< INT >(window_position.x), static_cast< INT >(window_position.y), m_window_size.GetWidth(), m_window_size.GetHeight(),
								  NULL, NULL, m_app_instance, NULL);

	// Show window.
	ShowWindow(m_app_window, SW_SHOW);
	SetForegroundWindow(m_app_window);
	SetFocus(m_app_window);

	return TRUE;
}


VOID Engine::TerminateWindows()
{
	// Show cursor.
	ShowCursor(TRUE);

	// Destroy the window.
	DestroyWindow(m_app_window);
	m_app_window = NULL;

	// Unregister the window class.
	UnregisterClass(m_app_name, m_app_instance);
}


BOOL Engine::InitializeScene()
{
	// Initialize camera.
	m_camera = new Camera(D3DX_PIF / 4.0f, static_cast< FLOAT >(m_window_size.GetWidth() / m_window_size.GetHeight()), 0.01f, 100.0f);
	assert(m_camera);

	m_camera->SetPosition(0.0f, 1.8f, 0.0f);

    // Load and initialize model.
    Assimp::Importer importer;

	const aiScene* ai_scene = importer.ReadFile("Models\\studio_final.obj", aiProcess_ConvertToLeftHanded);
	//const aiScene* ai_scene = importer.ReadFile("Models\\final_house.obj", aiProcess_ConvertToLeftHanded);

    assert(ai_scene);

    m_scene = new Scene(m_camera);    
    assert(m_scene);

	BOOL result = m_scene->build(ai_scene);
	assert(result);

	std::vector< Mesh* > meshes;
	CONST std::vector< Mesh* > opaques = m_scene->opaqueMeshes();
	CONST std::vector< Mesh* > transparents = m_scene->transparentMeshes();
	meshes.insert(meshes.end(), opaques.begin(), opaques.end());	
	meshes.insert(meshes.end(), transparents.begin(), transparents.end());

	for(std::vector< Mesh* >::const_iterator it = meshes.begin(); it != meshes.end(); ++it)
	{
		Mesh* mesh = *it;
		VertexBuffer* vbuffer = mesh->GetVertexBuffer();
		IndexBuffer* ibuffer = mesh->GetIndexBuffer();

		if(!vbuffer->IsBinded())
			m_renderer->Bind(*vbuffer);

		if(!ibuffer->IsBinded())
			m_renderer->Bind(*ibuffer);
	}

	return TRUE;
}


VOID Engine::TerminateScene()
{
	// Terminate model.
	if(m_scene)
	{
		std::vector< Mesh* > meshes;
		CONST std::vector< Mesh* > opaques = m_scene->opaqueMeshes();
		CONST std::vector< Mesh* > transparents = m_scene->transparentMeshes();
		meshes.insert(meshes.end(), opaques.begin(), opaques.end());
		meshes.insert(meshes.end(), transparents.begin(), transparents.end());

		for(std::vector< Mesh* >::const_iterator it = meshes.begin(); it != meshes.end(); ++it)
		{
			Mesh* mesh = *it;
			VertexBuffer* vbuffer = mesh->GetVertexBuffer();
			IndexBuffer* ibuffer = mesh->GetIndexBuffer();

			if(vbuffer->IsBinded())
				m_renderer->Unbind(*vbuffer);

			if(ibuffer->IsBinded())
				m_renderer->Unbind(*ibuffer);
		}

		delete m_scene;
		m_scene = NULL;
	}

	// Terminate camera.
	if(m_camera)
	{
		delete m_camera;
		m_camera = NULL;
	}
}


BOOL Engine::InitializeShaders()
{
	BOOL result = FALSE;

	// SPML shader.
	result = SPMLShader::Load();
	assert(result);

	m_spml_shader = new SPMLShader();
	assert(m_spml_shader);

	result = m_renderer->Bind(*m_spml_shader);
	assert(result);

	m_renderer->SetShader(m_spml_shader);

	// MPML shader.
	result = MPMLShader::Load();
	assert(result);

	m_mpml_shader = new MPMLShader();
	assert(m_mpml_shader);

	result = m_renderer->Bind(*m_mpml_shader);
	assert(result);

	// GBuffer shader.
	result = GBufferShader::Load();
	assert(result);

	m_gbuffer_shader = new GBufferShader();
	assert(m_gbuffer_shader);

	result = m_renderer->Bind(*m_gbuffer_shader);
	assert(result);

	// Deferred lighting shader.
	result = DeferredLightingShader::Load();
	assert(result);

	m_deflight_shader = new DeferredLightingShader();
	assert(m_deflight_shader);

	result = m_renderer->Bind(*m_deflight_shader);
	assert(result);

	// RT to screen shader.
	result = RT2ScreenShader::Load();
	assert(result);

	m_rt2screen_shader = new RT2ScreenShader();
	assert(m_rt2screen_shader);

	result = m_renderer->Bind(*m_rt2screen_shader);
	assert(result);

	// HDR Shader.
	result = HDRShader::Load();
	assert(result);
	
	m_hdr_shader = new HDRShader();
	assert(m_hdr_shader);

	result = m_renderer->Bind(*m_hdr_shader);
	assert(result);

	return TRUE;
}


VOID Engine::TerminateShaders()
{
	// SPML shader.
	if(m_spml_shader)
	{
		if(m_spml_shader->IsBinded())
			m_renderer->Unbind(*m_spml_shader);

		delete m_spml_shader;
		m_spml_shader = NULL;
	}

	if(SPMLShader::IsLoaded())
		SPMLShader::Unload();

	// MPML shader.
	if(m_mpml_shader)
	{
		if(m_mpml_shader->IsBinded())
			m_renderer->Unbind(*m_mpml_shader);

		delete m_mpml_shader;
		m_mpml_shader = NULL;
	}

	if(MPMLShader::IsLoaded())
		MPMLShader::Unload();

	// GBuffer shader.
	if(m_gbuffer_shader)
	{
		if(m_gbuffer_shader->IsBinded())
			m_renderer->Unbind(*m_gbuffer_shader);

		delete m_gbuffer_shader;
		m_gbuffer_shader = NULL;
	}

	// Deferred lighting shader.
	if(m_deflight_shader)
	{
		if(m_deflight_shader->IsBinded())
			m_renderer->Unbind(*m_deflight_shader);

		delete m_deflight_shader;
		m_deflight_shader = NULL;
	}

	// RT to screen shader.
	if(m_rt2screen_shader)
	{
		if(m_rt2screen_shader->IsBinded())
			m_renderer->Unbind(*m_rt2screen_shader);

		delete m_rt2screen_shader;
		m_rt2screen_shader = NULL;
	}

	// HDR shader.
	if(m_hdr_shader)
	{
		if(m_hdr_shader->IsBinded())
			m_renderer->Unbind(*m_hdr_shader);

		delete m_hdr_shader;
		m_hdr_shader = NULL;
	}
}


BOOL Engine::InitializeExtras()
{	
	BOOL result = FALSE;

	// Initialize HDR rendering target.
	UINT width = m_window_size.GetWidth();
	UINT width_div = 0;
	for(width_div = 0; width > 1; ++width_div)
		width = static_cast< UINT >(ceilf(width / 2.0f));

	UINT height = m_window_size.GetHeight();
	UINT height_div = 0;
	for(height_div = 0; height > 1; ++height_div)
		height = static_cast< UINT >(ceilf(height / 2.0f));

	m_hdr_target = new RenderTarget(m_window_size, DXGI_FORMAT_R16G16B16A16_FLOAT, D3D10_RESOURCE_MISC_GENERATE_MIPS, 0, max(width_div, height_div));
	assert(m_hdr_target);

	result = m_renderer->Bind(*m_hdr_target);
	assert(result);

	// Initialize GBuffer.
	for(UINT8 i = 0; i < GBUFFER_RT_COUNT; ++i)
	{
		m_gbuffer[i] = new RenderTarget(m_window_size, DXGI_FORMAT_R16G16B16A16_FLOAT, 0, 0, 1);
		assert(m_gbuffer[i]);

		result = m_renderer->Bind(*m_gbuffer[i]);
		assert(result);
	}

	return TRUE;
}


VOID Engine::TerminateExtras()
{
	// HDR Render target.
	if(m_hdr_target)
	{
		if(m_hdr_target->IsBinded())
			m_renderer->Unbind(*m_hdr_target);

		delete m_hdr_target;
		m_hdr_target = NULL;
	}

	// GBuffer.
	for(UINT8 i = 0; i < GBUFFER_RT_COUNT; ++i)
	{
		if(m_gbuffer[i])
		{
			if(m_gbuffer[i]->IsBinded())
				m_renderer->Unbind(*m_gbuffer[i]);

			delete m_gbuffer[i];
			m_gbuffer[i] = NULL;
		}
	}
}


BOOL Engine::Update()
{
	// Update inputs.
	m_input->Update();

	// Scene update.
	m_scene->update();

	// Commands.
	//-----------------------------------------------
	// Exit.
	if(m_input->ExitPressed())
		PostQuitMessage(EXIT_SUCCESS);

	// HDR.
	if(m_input->HDRPressed())
	{
		BOOL next_hdr = !m_hdr;
		m_hdr = next_hdr;
	}

	// Render type.
	if(m_input->RenderTypePressed())
	{
		RenderType next_render_type = RenderType((m_render_type + 1) % MAX_RENDER_TYPE);
		m_render_type = next_render_type;
	}

	// RT Switching.
	if(m_input->NextRTPressed())
	{
		DeferredRenderingTarget next_rt = DeferredRenderingTarget((m_rt + 1) % MAX_DEFERRED_RT);
		m_rt = next_rt;
	}

	// FPS Display.
	if(m_input->DisplayFPS())
	{
		bool dummy = FALSE;
		dummy = dummy;
	}

	// Light management.
	if(m_input->NewLight())
	{
		m_scene->newLight();
	}

	if(m_input->DeleteLight())
	{
		m_scene->deleteLight();
	}
		
	// Free chase camera.
	//-----------------------------------------------
	CONST FLOAT dt = s_loop_delay / 1000.0f;
	CONST FLOAT dp = dt * 2.0f;

	// Movement.
	if(m_input->ForwardPressed())
		m_camera->Translate(0, 0, dp);

	if(m_input->BackwardPressed())
		m_camera->Translate(0, 0, -dp);

	if(m_input->StrafeLeftPressed())
		m_camera->Translate(-dp, 0, 0);

	if(m_input->StrafeRightPressed())
		m_camera->Translate(dp, 0, 0);
	
	// Rotation.
	if(m_input->CameraControlPressed())
	{
		// Window metrics.
		RECT metrics;
		GetWindowRect(m_app_window, &metrics);

		assert((metrics.left + metrics.right) < D3D10_FLOAT32_MAX);
		assert((metrics.top + metrics.bottom) < D3D10_FLOAT32_MAX);
		assert((metrics.right - metrics.left) < D3D10_FLOAT32_MAX);
		assert((metrics.bottom - metrics.top) < D3D10_FLOAT32_MAX);

		CONST FLOAT x = static_cast< FLOAT >(metrics.left + metrics.right) / 2;
		CONST FLOAT y = static_cast< FLOAT >(metrics.top + metrics.bottom) / 2;
		CONST FLOAT width = static_cast< FLOAT >(metrics.right - metrics.left);
		CONST FLOAT height = static_cast< FLOAT >(metrics.bottom - metrics.top);

		m_camera->Rotate(0.0f, 1.0f, 0.0f, m_input->MouseX() / width * D3DX_PIF);
		m_camera->Rotate(1.0f, 0.0f, 0.0f, m_input->MouseY() / height * D3DX_PIF);

		// Keep cursor at window's center.
		SetCursorPos(static_cast< INT >(x), static_cast< INT >(y));
	}
	
	return TRUE;
}


BOOL Engine::Render() CONST
{
	// Clearing.
	m_renderer->ClearBackBuffer();
	m_renderer->ClearDepthBuffer();
	m_renderer->ClearStencilBuffer();

	// Rendering.
	BOOL result = FALSE;

	switch(m_render_type)
	{
	case SPML:
		result = RenderSPML();
		break;

	case MPML:
		result = RenderMPML();
		break;

	case DEFERRED_RENDERING:
		result = RenderDeferred();
		break;
	}
	
	// Swap.
	m_renderer->Swap();

	return result;
}


BOOL Engine::RenderSPML() CONST
{
	// No need to render if there's no lights.
	if(m_scene->lights().size() == 0)
		return TRUE;

	BOOL result = FALSE;

	// Setup.
	Matrix4x4 world = Matrix4x4::CreateIdentity();
	Matrix4x4 view = m_camera->GetView().InverseOrthonormal();

	m_renderer->SetShader(m_spml_shader);

	CONST std::vector< CONST Light* > lights = m_scene->lights();
	result = m_spml_shader->SetLightBuffer(lights, world);
	assert(result);

	result = m_spml_shader->SetFrameBuffer(m_camera->GetPosition(),
										   world,
										   view,
										   m_camera->GetProjection());

	assert(result);

	if(m_hdr)
	{
		m_hdr_target->ClearRenderTarget();
		m_renderer->SetRenderTargets(1, &m_hdr_target);
	}

	// Single pass for opaques.
	m_renderer->SetAdditiveBlending(FALSE);

	CONST std::vector< Mesh* > opaques = m_scene->opaqueMeshes();

	for(std::vector< Mesh* >::const_iterator it = opaques.begin(); it != opaques.end(); ++it)
	{
		CONST Mesh* mesh = *it;
		VertexBuffer* vbuffer = mesh->GetVertexBuffer();
		IndexBuffer* ibuffer = mesh->GetIndexBuffer();

		if(vbuffer->IsBinded())
			m_renderer->SetVertexBuffer(vbuffer);

		if(ibuffer->IsBinded())
			m_renderer->SetIndexBuffer(ibuffer);

		CONST Material* material = mesh->GetMaterial();
		
		if(material)
			m_spml_shader->SetMaterialBuffer(*material);

		m_renderer->Render();
	}

	// Single pass for transparents.
	m_renderer->SetAdditiveBlending(TRUE);

	CONST std::vector< Mesh* > transparents = m_scene->transparentMeshes();

	for(std::vector< Mesh* >::const_iterator it = transparents.begin(); it != transparents.end(); ++it)
	{
		CONST Mesh* mesh = *it;
		VertexBuffer* vbuffer = mesh->GetVertexBuffer();
		IndexBuffer* ibuffer = mesh->GetIndexBuffer();

		if(vbuffer->IsBinded())
			m_renderer->SetVertexBuffer(vbuffer);

		if(ibuffer->IsBinded())
			m_renderer->SetIndexBuffer(ibuffer);

		CONST Material* material = mesh->GetMaterial();
		
		if(material)
			m_spml_shader->SetMaterialBuffer(*material);

		m_renderer->Render();
	}

	// HDR Pass.
	if(m_hdr)
	{
		// Set output to back buffer.
		m_renderer->SetBackbuffer(TRUE);
		
		// Setup HDR shader settings.
		m_renderer->SetShader(m_hdr_shader);
		m_renderer->SetAdditiveBlending(FALSE);
		m_hdr_shader->SetSource(m_hdr_target);
		m_renderer->SetQuadBuffers();

		// Render.
		m_renderer->ClearDepthBuffer();
		m_renderer->Render();
	}

	return TRUE;
}


BOOL Engine::RenderMPML() CONST
{
	// No need to render if there's no lights.
	if(m_scene->lights().size() == 0)
		return TRUE;

	BOOL result = FALSE;

	CONST std::vector< Mesh* > opaques = m_scene->opaqueMeshes();

	// Setup.
	Matrix4x4 world = Matrix4x4::CreateIdentity();
	Matrix4x4 view = m_camera->GetView().InverseOrthonormal();

	m_renderer->SetShader(m_mpml_shader);								// This is not good (should be a special shader for depth pre pass).
	result = m_mpml_shader->SetFrameBuffer(m_camera->GetPosition(),
										   world, view,
										   m_camera->GetProjection());
	
	assert(result);

	result = m_mpml_shader->SetLightBuffer(*m_scene->lights().at(0), world);
	assert(result);

	// Setup depth prepass.
	m_renderer->SetBackbuffer(FALSE);
	m_renderer->ClearDepthBuffer();
	m_renderer->SetAdditiveBlending(FALSE);

	// Depth prepass.
	for(std::vector< Mesh* >::const_iterator it = opaques.begin(); it != opaques.end(); ++it)
	{
		CONST Mesh* mesh = *it;
		VertexBuffer* vbuffer = mesh->GetVertexBuffer();
		IndexBuffer* ibuffer = mesh->GetIndexBuffer();

		if(vbuffer->IsBinded())
			m_renderer->SetVertexBuffer(vbuffer);

		if(ibuffer->IsBinded())
			m_renderer->SetIndexBuffer(ibuffer);

		m_renderer->Render();
	}

	// Setup lightning pass.
	if(m_hdr)
	{
		// Clear and set HDR rendering target.
		m_hdr_target->ClearRenderTarget();
		m_renderer->SetRenderTargets(1, &m_hdr_target);
	}
	else
	{
		// Enable back buffer.
		m_renderer->SetBackbuffer(TRUE);
	}

	m_renderer->SetAdditiveBlending(TRUE);

	// Lightning pass.
	for(UINT i = 1; i < m_scene->lights().size(); ++i)
	{
		result = m_mpml_shader->SetLightBuffer(*m_scene->lights().at(i), world);
		assert(result);

		for(std::vector< Mesh* >::const_iterator it = opaques.begin(); it != opaques.end(); ++it)
		{
			CONST Mesh* mesh = *it;
			VertexBuffer* vbuffer = mesh->GetVertexBuffer();
			IndexBuffer* ibuffer = mesh->GetIndexBuffer();

			if(vbuffer->IsBinded())
				m_renderer->SetVertexBuffer(vbuffer);

			if(ibuffer->IsBinded())
				m_renderer->SetIndexBuffer(ibuffer);

			CONST Material* material = mesh->GetMaterial();
		
			if(material)
				m_mpml_shader->SetMaterialBuffer(*material);

			m_renderer->Render();
		}
	}

	// Lightning pass for transparents.
	CONST std::vector< Mesh* > transparents = m_scene->transparentMeshes();

	for(UINT i = 0; i < m_scene->lights().size(); ++i)
	{
		result = m_mpml_shader->SetLightBuffer(*m_scene->lights().at(i), world);
		assert(result);

		for(std::vector< Mesh* >::const_iterator it = transparents.begin(); it != transparents.end(); ++it)
		{
			CONST Mesh* mesh = *it;
			VertexBuffer* vbuffer = mesh->GetVertexBuffer();
			IndexBuffer* ibuffer = mesh->GetIndexBuffer();

			if(vbuffer->IsBinded())
				m_renderer->SetVertexBuffer(vbuffer);

			if(ibuffer->IsBinded())
				m_renderer->SetIndexBuffer(ibuffer);

			CONST Material* material = mesh->GetMaterial();
		
			if(material)
				m_mpml_shader->SetMaterialBuffer(*material);

			m_renderer->Render();
		}
	}

	// HDR Pass.
	if(m_hdr)
	{
		// Set output to back buffer.
		m_renderer->SetBackbuffer(TRUE);
		
		// Setup HDR shader settings.
		m_renderer->SetShader(m_hdr_shader);
		m_renderer->SetAdditiveBlending(FALSE);
		m_hdr_shader->SetSource(m_hdr_target);
		m_renderer->SetQuadBuffers();

		// Render.
		m_renderer->ClearDepthBuffer();
		m_renderer->Render();
	}

	return TRUE;
}


BOOL Engine::RenderDeferred() CONST
{
	// No need to render if there's no lights.
	if(m_scene->lights().size() == 0)
		return TRUE;

	// Geometry pass setup.
	m_renderer->SetShader(m_gbuffer_shader);
	m_renderer->SetRenderTargets(GBUFFER_RT_COUNT, m_gbuffer);

	for(UINT i = 0; i < GBUFFER_RT_COUNT; ++i)
		m_gbuffer[i]->ClearRenderTarget();

	BOOL result = FALSE;
	Matrix4x4 world = Matrix4x4::CreateIdentity();
	Matrix4x4 view = m_camera->GetView().InverseOrthonormal();

	result = m_gbuffer_shader->SetFrameBuffer(m_camera->GetPosition(),
											  world, view,
										      m_camera->GetProjection());
	assert(result);

	// Opaque rendering initialization.
	CONST std::vector< Mesh* > opaques = m_scene->opaqueMeshes();
	m_renderer->SetAdditiveBlending(FALSE);

	// Geometry pass.
	for(std::vector< Mesh* >::const_iterator it = opaques.begin(); it != opaques.end(); ++it)
	{
		CONST Mesh* mesh = *it;
		VertexBuffer* vbuffer = mesh->GetVertexBuffer();
		IndexBuffer* ibuffer = mesh->GetIndexBuffer();

		if(vbuffer->IsBinded())
			m_renderer->SetVertexBuffer(vbuffer);

		if(ibuffer->IsBinded())
			m_renderer->SetIndexBuffer(ibuffer);

		CONST Material* material = mesh->GetMaterial();
		
		if(material)
			m_gbuffer_shader->SetMaterialBuffer(*material);

		m_renderer->Render();
	}

	// Lightning pass setup.
	if(m_hdr)
	{
		// Clear and set HDR rendering target.
		m_hdr_target->ClearRenderTarget();
		m_renderer->SetRenderTargets(1, &m_hdr_target);
	}
	else
	{
		m_renderer->SetBackbuffer(TRUE);
	}

	m_renderer->SetQuadBuffers();

	// Rendering target to screen pass.
	if(m_rt != COMPOSITION)
	{
		m_renderer->SetShader(m_rt2screen_shader);
		m_renderer->SetDepthbuffer(FALSE);
		m_rt2screen_shader->SetSource(m_gbuffer[m_rt]);

		m_renderer->Render();
		
		m_renderer->SetDepthbuffer(TRUE);
	}
	// Lightning pass.
	else
	{
		m_renderer->SetShader(m_deflight_shader);
		m_renderer->SetDepthbuffer(FALSE);
		m_deflight_shader->SetGBuffer(m_gbuffer);	

		m_renderer->SetAdditiveBlending(TRUE);

		for(UINT i = 0; i < m_scene->lights().size(); ++i)
		{
			m_deflight_shader->SetLightBuffer(*m_scene->lights().at(i), world);
			m_renderer->Render();
		}

		// Transparent MPML pass.
		m_renderer->SetShader(m_mpml_shader);	
		m_renderer->SetDepthbuffer(TRUE);
		result = m_mpml_shader->SetFrameBuffer(m_camera->GetPosition(),
											   world, view,
										       m_camera->GetProjection());

		CONST std::vector< Mesh* > transparents = m_scene->transparentMeshes();

		for(UINT i = 0; i < m_scene->lights().size(); ++i)
		{
			result = m_mpml_shader->SetLightBuffer(*m_scene->lights().at(i), world);
			assert(result);

			for(std::vector< Mesh* >::const_iterator it = transparents.begin(); it != transparents.end(); ++it)
			{
				CONST Mesh* mesh = *it;
				VertexBuffer* vbuffer = mesh->GetVertexBuffer();
				IndexBuffer* ibuffer = mesh->GetIndexBuffer();

				if(vbuffer->IsBinded())
					m_renderer->SetVertexBuffer(vbuffer);

				if(ibuffer->IsBinded())
					m_renderer->SetIndexBuffer(ibuffer);

				CONST Material* material = mesh->GetMaterial();
		
				if(material)
					m_mpml_shader->SetMaterialBuffer(*material);

				m_renderer->Render();
			}
		}
	}

	// HDR Pass.
	if(m_hdr)
	{
		// Set output to back buffer.
		m_renderer->SetBackbuffer(TRUE);
		
		// Setup HDR shader settings.
		m_renderer->SetShader(m_hdr_shader);
		m_renderer->SetAdditiveBlending(FALSE);
		m_hdr_shader->SetSource(m_hdr_target);
		m_renderer->SetQuadBuffers();

		// Render.
		m_renderer->ClearDepthBuffer();
		m_renderer->Render();
	}

	return TRUE;
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
	switch(umsg)
	{
	case WM_DESTROY:
		PostQuitMessage(EXIT_SUCCESS);
		return 0;

	case WM_CLOSE:
		PostQuitMessage(EXIT_SUCCESS);
		return 0;

	default:
		return DefWindowProc(hwnd, umsg, wparam, lparam);
	}
}
